﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal sealed class GroupLevelEnumerator : ConversionEnumerator<DataPath, DataPath>, IGroupEnumerator
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid JumpMoveAndFetchCoreCallId = DataUtils.CreateCallId();
    private static readonly Guid GetCurrentItemHeadersAndContentCallId = DataUtils.CreateCallId();
    private static readonly Guid GetPreviousItemFootersCallId = DataUtils.CreateCallId();
    private static readonly Guid GetHeadersFootersForItemsCallId = DataUtils.CreateCallId();


    private static readonly Guid CountCallId = DataUtils.CreateCallId();
    private static readonly Guid ResolveCountCallId = DataUtils.CreateCallId();
    private static readonly Guid SubCountCallId = DataUtils.CreateCallId();
    private static readonly Guid SubGroupCountsCallId = DataUtils.CreateCallId();
    private static readonly Guid RangeCountCallId = DataUtils.CreateCallId();
    private static readonly Guid HeadersFootersCountCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal GroupLevelEnumerator( IAsyncEnumerator<DataPath> childEnumerator, IContextualGroupDataHelper groupHelper )
      : base( childEnumerator )
    {
      if( groupHelper == null )
        throw new ArgumentNullException( "groupHelper" );

      m_groupHelper = groupHelper;
    }

    #endregion

    #region CachedPathProvider Internal Property

    internal Func<DataSourceContext, DataPath, DataPath> CachedPathProvider
    {
      get;
      set;
    }

    #endregion

    #region Begin/End JumpMoveAndFetch Methods

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  GroupLevelEnumerator.JumpMoveAndFetchCallId,
                                                  userCallback,
                                                  asyncState,
                                                  parameters );
      DataSourceContext context = parameters.Context;

      // Nothing needs to be done if there is no group header or footer and there is no group collapsed.
      if( !GroupLevelEnumerator.AddOrRemoveItems( context.GroupContext ) )
      {
        asyncResult.Start(
          ( cb, st ) => this.BaseBeginJumpMoveAndFetch( parameters, cb, st ),
          ( par, iar ) => new DependentCallResult( this.BaseEndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar ) ) );
      }
      else if( ( parameters.ExpectedMoveCount != 0 )
            && ( ( parameters.ExpectedFetchCount == 0
              || ( parameters.GetMoveUnit() != parameters.GetFetchUnit() ) ) ) )
      {
        JumpMoveAndFetchParameters moveParameters = parameters.CreateWithNewFetchCount( parameters.GetMoveUnit() );

        asyncResult.Start(
          ( cb, st ) => this.BeginJumpMoveAndFetchCore( moveParameters, cb, st ),
          this.TwoWayMoveCallback );
      }
      else
      {
        asyncResult.Start(
          ( cb, st ) => this.BeginJumpMoveAndFetchCore( parameters, cb, st ),
          ( par, iar ) => new DependentCallResult( this.EndJumpMoveAndFetchCore( ( JumpMoveAndFetchAsyncResult )iar ) ) );
      }

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<DataPath>>();
    }

    private DependentCallResult TwoWayMoveCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = ( JumpMoveAndFetchAsyncResult )parentAsyncResult;
      JumpMoveAndFetchParameters mainAsyncParams = mainAsyncResult.GetParameters();
      JumpMoveAndFetchResult<DataPath> moveResult = this.EndJumpMoveAndFetchCore( ( JumpMoveAndFetchAsyncResult )asyncResult );

      if( ( m_disposed ) || ( moveResult == null ) )
        return new DependentCallResult( null );

      // The move part failed.
      if( !moveResult.Result )
        return new DependentCallResult(
          new JumpMoveAndFetchResult<DataPath>(
            mainAsyncResult,
            moveResult.ActualMoveCount,
            moveResult.FetchedItems,
            moveResult.Result,
            moveResult.EndPositionObject,
            moveResult.EndPositionOffset,
            moveResult.TotalCount ) );

      // There is no fetch part.
      if( mainAsyncParams.ExpectedFetchCount == 0 )
        return new DependentCallResult(
          new JumpMoveAndFetchResult<DataPath>(
            mainAsyncResult,
            moveResult.ActualMoveCount,
            new DataPath[ 0 ],
            moveResult.Result,
            moveResult.EndPositionObject,
            moveResult.EndPositionOffset,
            moveResult.TotalCount ) );

      JumpMoveAndFetchParameters fetchParameters = new JumpMoveAndFetchParameters(
                                                     moveResult.EndPositionObject,
                                                     0,
                                                     mainAsyncParams.ExpectedFetchCount,
                                                     false,
                                                     mainAsyncParams.Context );

      return new DependentCallResult(
        null,
        ( cb, st ) => this.BeginJumpMoveAndFetchCore( fetchParameters, cb, st ),
        this.TwoWayFetchCallback );
    }

    private DependentCallResult TwoWayFetchCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult )
    {
      JumpMoveAndFetchAsyncResult mainAsyncResult = ( JumpMoveAndFetchAsyncResult )parentAsyncResult;
      JumpMoveAndFetchResult<DataPath> fetchResult = this.EndJumpMoveAndFetchCore( ( JumpMoveAndFetchAsyncResult )asyncResult );

      if( ( m_disposed ) || ( fetchResult == null ) )
        return new DependentCallResult( null );

      return new DependentCallResult(
        new JumpMoveAndFetchResult<DataPath>(
          mainAsyncResult,
          mainAsyncResult.ExpectedMoveCount,
          fetchResult.FetchedItems,
          fetchResult.Result,
          fetchResult.EndPositionObject,
          fetchResult.EndPositionOffset,
          fetchResult.TotalCount ) );
    }

    private JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetchCore( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      GroupAsyncResult asyncResult = new GroupAsyncResult( GroupLevelEnumerator.JumpMoveAndFetchCoreCallId, userCallback, asyncState, parameters );
      JumpMoveAndFetchParameters newParameters = GroupLevelEnumerator.OptimizeParameters( parameters );

      asyncResult.Start(
        ( cb, st ) => this.BaseBeginJumpMoveAndFetch( newParameters, cb, st ),
        this.InitialFetchCallback );

      return asyncResult;
    }

    private JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetchCore( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      jumpMoveAndFetchAsyncResult.ValidateCallEnd( GroupLevelEnumerator.JumpMoveAndFetchCoreCallId );

      return jumpMoveAndFetchAsyncResult.GetCallResult<JumpMoveAndFetchResult<DataPath>>();
    }

    #endregion

    #region Base(Begin/End)JumpMoveAndFetch methods

    private JumpMoveAndFetchAsyncResult BaseBeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    private JumpMoveAndFetchResult<DataPath> BaseEndJumpMoveAndFetch( IAsyncResult childAr )
    {
      return base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )childAr );
    }

    #endregion

    #region Begin/End GetGroupItems

    public IAsyncResult BeginGetBaseItems( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    public JumpMoveAndFetchResult<DataPath> EndGetBaseItems( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      return base.EndJumpMoveAndFetch( jumpMoveAndFetchAsyncResult );
    }

    #endregion

    #region Sub Enumerator Fetch Callbacks

    private DependentCallResult InitialFetchCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult )
    {
      GroupAsyncResult groupAsyncResult = ( GroupAsyncResult )parentAsyncResult;
      JumpMoveAndFetchResult<DataPath> result = this.BaseEndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )asyncResult );

      if( ( m_disposed ) || ( result == null ) )
        return new DependentCallResult( null );

      Debug.Assert( result.ExpectedFetchCount != 0 );

      ChildResultEnumerator childResult = new ChildResultEnumerator( result );

      //Handle the fail move case
      if( !childResult.HasCurrent )
        return this.CompleteOperation( groupAsyncResult, childResult );

      DataSourceContext context = groupAsyncResult.Context;
      DataPath previousPath = null;
      DataPath completeHeaderPath;
      DataPath startingPath;
      Nullable<StartPosition> startPosition;

      GroupLevelEnumerator.ExtractFetchSourcePath(
        groupAsyncResult.GetParameters(),
        out completeHeaderPath,
        out startingPath,
        out startPosition );

      if( completeHeaderPath != null )
      {
        Debug.Assert( groupAsyncResult.ExpectedMoveCount == 0 );

        DataPath headerPath = GroupLevelEnumerator.GetHeaderPath( completeHeaderPath );
        HeaderFooterDataItem headerItem = ( HeaderFooterDataItem )headerPath.LastChild;

        previousPath = completeHeaderPath;

        if( ( groupAsyncResult.ExpectedFetchCount < 0 ) == headerItem.IsHeader )
        {
          DataPath headerGroup = headerPath.ParentPath;
          DataPath currentPath = childResult.Current;

          if( headerGroup.IsAncestorOf( currentPath ) )
            return new DependentCallResult(
              null,
              ( cb, st ) => this.BeginGetHeadersFootersForItem( cb, st, context, previousPath, Math.Abs( groupAsyncResult.ExpectedFetchCount ), groupAsyncResult.GetFetchUnit(), headerItem.IsHeader ),
              ( par, iar ) => this.HeadersStartPositionFetchCallback( par, iar, headerPath ) );
        }
      }
      else if( ( groupAsyncResult.ExpectedMoveCount == 0 ) && ( startingPath != null ) )
      {
        if( startPosition != null )
        {
          previousPath = startingPath.ParentPath;
        }
      }
      else if( ( Math.Abs( groupAsyncResult.ExpectedMoveCount ) == 1 ) && ( groupAsyncResult.SourcePosition.IsOutOfBound ) )
      {
        previousPath = new DataPath( new DataItem[ 0 ] );
      }

      return new DependentCallResult(
        null,
        ( cb, st ) => this.BeginGetCurrentItemHeadersAndContent( cb, st, context, 1, previousPath, childResult.Current, Math.Abs( groupAsyncResult.ExpectedFetchCount ), groupAsyncResult.GetFetchUnit() ),
        ( par, iar ) => this.CurrentItemHeadersAndContentFetchCallback( par, iar, childResult ) );
    }

    private DependentCallResult SubsequentFetchCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult, DataPath previousPath )
    {
      GroupAsyncResult groupAsyncResult = ( GroupAsyncResult )parentAsyncResult;
      JumpMoveAndFetchResult<DataPath> result = this.BaseEndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )asyncResult );

      if( ( m_disposed ) || ( result == null ) )
        return new DependentCallResult( null );

      Debug.Assert( result.ExpectedFetchCount != 0 );

      ChildResultEnumerator childResult = new ChildResultEnumerator( result );

      return new DependentCallResult(
        null,
        ( cb, st ) => this.BeginGetPreviousItemFooters( cb, st, groupAsyncResult.Context, 1, previousPath, childResult.Current, Math.Abs( groupAsyncResult.RemainingFetchCount ), groupAsyncResult.GetFetchUnit() ),
        ( par, iar ) => this.PreviousItemFootersCallback( par, iar, previousPath, childResult ) );
    }

    #endregion

    #region Header start position special case

    private DependentCallResult HeadersStartPositionFetchCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult, DataPath headerPath )
    {
      GroupAsyncResult groupAsyncResult = ( GroupAsyncResult )parentAsyncResult;
      SimpleFetchResult result = this.EndGetHeadersFootersForItem( asyncResult );

      if( ( m_disposed ) || ( result == null ) )
        return new DependentCallResult( null );

      groupAsyncResult.AddFetchResult( result );

      Debug.Assert( headerPath.LastChild is HeaderFooterDataItem );
      Debug.Assert( ( groupAsyncResult.ExpectedFetchCount < 0 ) == ( ( HeaderFooterDataItem )headerPath.LastChild ).IsHeader );

      DataPath groupPath = headerPath.ParentPath;

      //Fetch the next group.
      StartPosition startPosition = groupAsyncResult.GetFetchUnit() > 0
        ? StartPosition.After
        : StartPosition.Before;

      int fetchCount = groupAsyncResult.RemainingFetchCount;

      if( fetchCount == 0 )
      {
        fetchCount = groupAsyncResult.GetFetchUnit();
      }

      DataSourceContext context = groupAsyncResult.Context;

      JumpMoveAndFetchParameters newFetch =
        new JumpMoveAndFetchParameters(
          m_groupHelper.CreateSortPosition( context, groupPath, startPosition ),
          0,
          fetchCount,
          false,
          context );

      return new DependentCallResult( null,
        ( cb, st ) => this.BaseBeginJumpMoveAndFetch( newFetch, cb, st ),
        ( par, iar ) => this.SubsequentFetchCallback( par, iar, groupPath ) );

    }

    #endregion

    #region PreviousItem Footers Fetch Callbacks

    private DependentCallResult PreviousItemFootersCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult, DataPath previousItem, ChildResultEnumerator childResult )
    {
      GroupAsyncResult groupAsyncResult = ( GroupAsyncResult )parentAsyncResult;
      SimpleFetchResult result = this.EndGetPreviousItemFooters( asyncResult );

      if( ( m_disposed ) || ( result == null ) )
        return new DependentCallResult( null );

      groupAsyncResult.AddFetchResult( result );

      if( !childResult.HasCurrent )
        return this.CompleteOperation( groupAsyncResult, childResult );

      return new DependentCallResult( null,
        ( cb, st ) => this.BeginGetCurrentItemHeadersAndContent( cb, st, groupAsyncResult.Context, 1, previousItem, childResult.Current, Math.Abs( groupAsyncResult.RemainingFetchCount ), groupAsyncResult.GetFetchUnit() ),
        ( par, iar ) => this.CurrentItemHeadersAndContentFetchCallback( par, iar, childResult ) );
    }

    #endregion

    #region CurrentItemHeadersAndContent Fetch Callback

    private DependentCallResult CurrentItemHeadersAndContentFetchCallback( AsyncResult parentAsyncResult, IAsyncResult asyncResult, ChildResultEnumerator childResult )
    {
      GroupAsyncResult groupAsyncResult = ( GroupAsyncResult )parentAsyncResult;
      SimpleFetchResult result = this.EndGetCurrentItemHeadersAndContent( asyncResult );

      if( ( m_disposed ) || ( result == null ) )
        return new DependentCallResult( null );

      groupAsyncResult.AddFetchResult( result );

      if( groupAsyncResult.RemainingFetchCount == 0 )
        return this.CompleteOperation( groupAsyncResult, childResult );

      DependentCallResult callResult;
      DataPath previousPath = groupAsyncResult.CumulativeFetch.Last();

      if( !childResult.MoveNext() )
      {
        //ChildResult is completely used an more items are needed. Fetch the child again.
        JumpMoveAndFetchParameters newFetch =
          new JumpMoveAndFetchParameters(
            new ItemPosition( previousPath ),
            groupAsyncResult.GetFetchUnit(),
            groupAsyncResult.RemainingFetchCount,
            false,
            groupAsyncResult.Context );

        callResult = new DependentCallResult(
          null,
          ( cb, st ) => this.BaseBeginJumpMoveAndFetch( GroupLevelEnumerator.OptimizeParameters( newFetch ), cb, st ),
          ( par, iar ) => this.SubsequentFetchCallback( par, iar, previousPath ) );
      }
      else
      {
        callResult = new DependentCallResult(
          null,
          ( cb, st ) => this.BeginGetPreviousItemFooters( cb, st, groupAsyncResult.Context, 1, previousPath, childResult.Current, Math.Abs( groupAsyncResult.RemainingFetchCount ), groupAsyncResult.GetFetchUnit() ),
          ( par, iar ) => this.PreviousItemFootersCallback( par, iar, previousPath, childResult ) );
      }

      return callResult;
    }

    #endregion

    #region Begin/End CurrentItemHeadersAndContent Methods

    private IAsyncResult BeginGetCurrentItemHeadersAndContent(
      AsyncCallback userCallback,
      object asyncState,
      DataSourceContext context,
      int depth,
      DataPath previousItem,
      DataPath currentItem,
      int absoluteFetchCount,
      int fetchDirection )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( currentItem == null )
        throw new ArgumentNullException( "currentItem" );

      DataSourceGroupContext groupContext = context.GroupContext;

      if( currentItem.Depth <= groupContext.GroupDescriptions.Count )
        throw new ArgumentException( "The specified current item must be an expanded item.", "currentItem" );

      if( absoluteFetchCount < 0 )
        throw new ArgumentOutOfRangeException( "absoluteFetchCount", "absoluteFetchCount must be greater than or equal to zero." );

      if( Math.Abs( fetchDirection ) != 1 )
        throw new ArgumentException( "fetchDirection must be 1 or -1.", "fetchDirection" );

      AsyncResult asyncResult = new AsyncResult( GroupLevelEnumerator.GetCurrentItemHeadersAndContentCallId, userCallback, asyncState );

      DataPath previousLevel = ( previousItem != null )
        ? previousItem.CreateAncestorOrSamePath( Math.Min( depth, previousItem.Depth ) )
        : null;

      DataPath currentLevel = currentItem.CreateAncestorOrSamePath( depth );

      if( groupContext.GroupCollections.IsCollapsedAtTargetLevel( currentLevel, currentLevel.Depth ) )
      {
        IEnumerable<DataPath> items;
        EstimateInt64 count;

        if( ( previousLevel == null ) || ( !object.Equals( previousLevel, currentLevel ) ) )
        {
          items = ( absoluteFetchCount != 0 )
            ? new DataPath[] { currentLevel }
            : new DataPath[ 0 ];

          count = 1;
        }
        else
        {
          items = new DataPath[ 0 ];
          count = 0;
        }

        asyncResult.StartSync( new SimpleFetchResult( items, count ) );
      }
      else
      {
        SimpleFetchResult fetchResult = new SimpleFetchResult();

        if( previousLevel != null )
        {
          DataPath getHeaderPath = null;

          if( !object.Equals( previousLevel, currentLevel ) )
          {
            //Normal case
            getHeaderPath = currentLevel;
          }
          else if( previousItem.Depth == ( depth + 1 ) && previousItem.LastChild is HeaderFooterDataItem )
          {
            //Handle the case where the previous item is a Header. Include it in the fetch.
            getHeaderPath = previousItem;

            Debug.Assert( ( ( HeaderFooterDataItem )previousItem.LastChild ).IsHeader == ( fetchDirection > 0 ) );
          }

          if( getHeaderPath != null )
          {
            Func<AsyncCallback, object, IAsyncResult> getHeaderFooters =
              ( cb, st ) =>
              {
                int remainingFetchCount = absoluteFetchCount - fetchResult.FetchedItems.Count;

                return this.BeginGetHeadersFootersForItem( cb, st, context, getHeaderPath, remainingFetchCount, fetchDirection, ( fetchDirection > 0 ) );
              };

            Func<AsyncResult, IAsyncResult, DependentCallResult> getHeadersFootersCallback =
              ( par, iar ) =>
              {
                SimpleFetchResult result = this.EndGetHeadersFootersForItem( iar );

                if( result == null )
                  return new DependentCallResult( null );

                fetchResult.Add( result );

                return new DependentCallResult( fetchResult );
              };

            asyncResult.AddDependentCall( getHeaderFooters, getHeadersFootersCallback );
          }
        }

        Func<AsyncCallback, object, IAsyncResult> getItemContent;
        Func<AsyncResult, IAsyncResult, DependentCallResult> getItemContentCallback;

        if( depth < groupContext.GroupDescriptions.Count )
        {
          getItemContent =
            ( cb, st ) =>
            {
              int remainingFetchCount = absoluteFetchCount - fetchResult.FetchedItems.Count;

              return this.BeginGetCurrentItemHeadersAndContent( cb, st, context, depth + 1, previousItem, currentItem, remainingFetchCount, fetchDirection );
            };

          getItemContentCallback =
            ( par, iar ) =>
            {
              SimpleFetchResult result = this.EndGetCurrentItemHeadersAndContent( iar );

              if( result == null )
                return new DependentCallResult( null );

              fetchResult.Add( result );

              return new DependentCallResult( fetchResult );
            };
        }
        else
        {
          getItemContent =
            ( cb, st ) =>
            {
              AsyncResult getContentAsyncResult = new AsyncResult( Guid.Empty, cb, st );

              getContentAsyncResult.StartSync( null );

              return getContentAsyncResult;
            };

          getItemContentCallback =
            ( par, iar ) =>
            {
              if( absoluteFetchCount > fetchResult.FetchedItems.Count )
              {
                fetchResult.Add( new DataPath[] { currentItem }, 1 );
              }
              else
              {
                fetchResult.Add( new DataPath[ 0 ], 1 );
              }

              return new DependentCallResult( fetchResult );
            };
        }

        asyncResult.AddDependentCall( getItemContent, getItemContentCallback );
        asyncResult.Start();
      }

      return asyncResult;
    }

    private SimpleFetchResult EndGetCurrentItemHeadersAndContent( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.GetCurrentItemHeadersAndContentCallId );

      return asyncResult.GetCallResult<SimpleFetchResult>();
    }

    #endregion

    #region Begin/End GetPreviousItemFooters Methods

    private IAsyncResult BeginGetPreviousItemFooters(
      AsyncCallback userCallback,
      object asyncState,
      DataSourceContext context,
      int depth,
      DataPath previousItem,
      DataPath currentItem,
      int absoluteFetchCount,
      int fetchDirection )
    {
      AsyncResult asyncResult = new AsyncResult( GroupLevelEnumerator.GetPreviousItemFootersCallId, userCallback, asyncState );
      SimpleFetchResult fetchResult = new SimpleFetchResult();

      if( depth > previousItem.Depth - 1 )
      {
        asyncResult.StartSync( fetchResult );
        return asyncResult;
      }

      DataPath currentLevel = ( currentItem != null ) ? currentItem.CreateAncestorPath( depth ) : null;
      DataPath previousLevel = previousItem.CreateAncestorPath( depth );

      if( depth < ( previousItem.Depth - 1 ) )
      {
        Func<AsyncCallback, object, IAsyncResult> getSubLevelItemFooters =
          ( cb, st ) => this.BeginGetPreviousItemFooters( cb, st, context, depth + 1, previousItem, currentItem, absoluteFetchCount, fetchDirection );

        Func<AsyncResult, IAsyncResult, DependentCallResult> getSubLevelItemFootersCallback =
          ( par, iar ) =>
          {
            SimpleFetchResult result = this.EndGetPreviousItemFooters( iar );

            if( result == null )
              return new DependentCallResult( null );

            fetchResult.Add( result );

            return new DependentCallResult( fetchResult );
          };

        asyncResult.AddDependentCall( getSubLevelItemFooters, getSubLevelItemFootersCallback );
      }

      if( !object.Equals( previousLevel, currentLevel ) )
      {
        asyncResult.AddDependentCall(
          ( cb, st ) =>
          {
            int remainingFetchCount = ( absoluteFetchCount - fetchResult.FetchedItems.Count );

            return this.BeginGetHeadersFootersForItem( cb, st, context, previousLevel, remainingFetchCount, fetchDirection, ( fetchDirection < 0 ) );
          },
          ( par, iar ) =>
          {
            SimpleFetchResult result = this.EndGetHeadersFootersForItem( iar );

            if( result == null )
              return new DependentCallResult( null );

            fetchResult.Add( result );

            return new DependentCallResult( fetchResult );
          } );
      }

      if( asyncResult.HasDependentCalls )
      {
        asyncResult.Start();
      }
      else
      {
        asyncResult.StartSync( fetchResult );
      }

      return asyncResult;
    }

    private SimpleFetchResult EndGetPreviousItemFooters( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.GetPreviousItemFootersCallId );

      return asyncResult.GetCallResult<SimpleFetchResult>();
    }

    #endregion

    #region Begin/End GetHeadersFootersForItem Methods

    private IAsyncResult BeginGetHeadersFootersForItem(
      AsyncCallback userCallbak,
      object asyncState,
      DataSourceContext context,
      DataPath path,
      int absoluteFetchCount,
      int fetchDirection,
      bool headers )
    {
      if( ( fetchDirection != 1 ) && ( fetchDirection != -1 ) )
        throw new ArgumentException( "fetchDirection must be 1 or -1.", "fetchDirection" );

      AsyncResult asyncResult = new AsyncResult( GroupLevelEnumerator.GetHeadersFootersForItemsCallId, userCallbak, asyncState );

      //Handle the case where the path refer to an header/footer item.
      bool pathReferToHeaderFooter = path.LastChild is HeaderFooterDataItem;

      DataPath groupPath = path;

      if( pathReferToHeaderFooter )
      {
        groupPath = groupPath.ParentPath;

        if( headers != ( ( HeaderFooterDataItem )path.LastChild ).IsHeader )
          throw new DataInternalException();
      }

      Debug.Assert( groupPath.LastChild is GroupDataItem );

      IAsyncEnumerator<DataPath> headersFootersEnum =
        ( headers )
          ? context.GroupContext.GetHeadersEnumerator( groupPath )
          : context.GroupContext.GetFootersEnumerator( groupPath );

      int expectedFetchCount =
        ( absoluteFetchCount == 0 )
          ? fetchDirection
          : ( absoluteFetchCount * fetchDirection );

      long initialMoveCount;
      SourcePosition sourcePosition;

      if( pathReferToHeaderFooter )
      {
        sourcePosition = new ItemPosition( path );
        initialMoveCount = 0;
      }
      else
      {
        initialMoveCount = fetchDirection;
        sourcePosition = ( fetchDirection > 0 ) ? SourcePosition.BeforeFirst : SourcePosition.AfterLast;
      }

      JumpMoveAndFetchParameters callParam = new JumpMoveAndFetchParameters( sourcePosition, initialMoveCount, expectedFetchCount, context );

      asyncResult.Start(
        ( cb, st ) => headersFootersEnum.BeginJumpMoveAndFetch( callParam, cb, st ),
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<DataPath> result = headersFootersEnum.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );
          SimpleFetchResult fetchResult;

          if( ( m_disposed ) || ( result == null ) )
            return new DependentCallResult( null );

          if( result.FetchedItems != null )
          {
            EstimateInt64 maxFetchCount = result.GetMaximumFetchCount();

            if( maxFetchCount != null )
            {
              maxFetchCount = maxFetchCount * result.GetFetchUnit();
            }
            else
            {
              maxFetchCount = result.TotalCount;
            }

            fetchResult = new SimpleFetchResult(
              result.FetchedItems.Take( absoluteFetchCount ),
              maxFetchCount );
          }
          else
          {
            fetchResult = new SimpleFetchResult( new DataPath[ 0 ], 0 );
          }

          return new DependentCallResult( fetchResult );
        } );

      return asyncResult;
    }

    private SimpleFetchResult EndGetHeadersFootersForItem( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.GetHeadersFootersForItemsCallId );

      return asyncResult.GetCallResult<SimpleFetchResult>();
    }

    #endregion

    #region Begin/End Count Methods

    public override IAsyncResult BeginCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      AsyncResult asyncResult = new AsyncResult( GroupLevelEnumerator.CountCallId, userCallback, asyncState );

      DataSourceContext context = parameters.Context;
      DataSourceGroupContext groupContext = context.GroupContext;

      EstimateInt64 totalCount = null;
      EstimateInt64 collapsedCount = null;
      IList<EstimateInt64> groupCount = null;
      IList<EstimateInt64> defaultHeadersCounts = null;
      IList<EstimateInt64> defaultFootersCounts = null;

      DependentCall totalCountCall = null;
      DependentCall countHeadersCall = null;
      DependentCall countFootersCall = null;
      DependentCall countGroupsCalls = null;
      DependentCall collapsedCountCall = null;


      totalCountCall =
        new DependentCall(
        ( cb, st ) => this.ChildEnumerator.BeginCount( parameters, cb, st ),
        ( par, iar ) =>
        {
          CountResult countResult = this.ChildEnumerator.EndCount( iar );

          if( ( m_disposed ) || ( countResult == null ) )
            return new DependentCallResult( null );

          bool haveCollapseAllState = groupContext.GroupCollections.Any( ( collection ) => collection.IsCollapsedByDefault );

          if( haveCollapseAllState || ( ( parameters.StartPosition != null ) && GroupLevelEnumerator.AddOrRemoveItems( groupContext ) ) )
            return new DependentCallResult( new CountResult( totalCount.ToEstimate() ) );

          totalCount = countResult.Count;

          if( totalCount == null )
            return new DependentCallResult( null );

          // There is nothing else to count if there is no grouping.
          if( !groupContext.AnyGroup )
            return new DependentCallResult( new CountResult( totalCount ) );

          return new DependentCallResult( null, countHeadersCall );
        } );

      countHeadersCall =
        new DependentCall(
          ( cb, st ) =>
          {
            Func<DataPath, IAsyncEnumerator> getEnumerator = ( dataPath ) => groupContext.GetHeadersEnumerator( dataPath );

            return this.BeginHeadersFootersCount( context, getEnumerator, 1, cb, st );
          },
          ( par, iar ) =>
          {
            defaultHeadersCounts = this.EndHeadersFootersCountForDepth( iar );

            if( m_disposed )
              return new DependentCallResult( null );

            return new DependentCallResult( null, countFootersCall );
          } );

      countFootersCall =
        new DependentCall(
          ( cb, st ) =>
          {
            Func<DataPath, IAsyncEnumerator> getEnumerator = ( dataPath ) => groupContext.GetFootersEnumerator( dataPath );

            return this.BeginHeadersFootersCount( context, getEnumerator, 1, cb, st );
          },
          ( par, iar ) =>
          {
            defaultFootersCounts = this.EndHeadersFootersCountForDepth( iar );

            if( m_disposed )
              return new DependentCallResult( null );

            return new DependentCallResult( null, countGroupsCalls );
          } );

      countGroupsCalls =
        new DependentCall(
        ( cb, st ) =>
        {
          SortPosition startPosition = parameters.StartPosition;
          int currentGroupCount = groupContext.GroupCollections.Count;

          // The query is done for all groups.
          if( startPosition == null )
          {
            startPosition = new SortPosition(
                              context.GetCurrentSortOrder().Take( currentGroupCount ).ToList(),
                              new List<object>( 0 ),
                              StartPosition.Before );
          }

          CountParameters newParameters = new CountParameters(
                                            startPosition,
                                            parameters.TargetGroup,
                                            parameters.Context );

          return this.BeginGroupCount( newParameters, cb, st );
        },
        ( par, iar ) =>
        {
          GroupCountResult result = this.EndGroupCount( iar );

          if( m_disposed )
            return new DependentCallResult( null );

          groupCount = ( result != null ) ? result.GroupCounts : ( IList<EstimateInt64> )null;

          return new DependentCallResult( null, collapsedCountCall );
        } );

      collapsedCountCall =
        new DependentCall(
          ( cb, st ) => this.BeginCollapsedGroupCount( context, null, false, false, defaultHeadersCounts, defaultFootersCounts, cb, st ),
          ( par, iar ) =>
          {
            collapsedCount = this.EndCollapsedGroupCount( iar );

            if( m_disposed )
              return new DependentCallResult( null );

            EstimateInt64 adjustedTotalCount = this.CalculateTotalCount(
              groupContext,
              totalCount,
              collapsedCount,
              groupCount,
              defaultHeadersCounts,
              defaultFootersCounts );

            return new DependentCallResult( new CountResult( adjustedTotalCount ) );
          } );


      asyncResult.Start( totalCountCall );

      return asyncResult;
    }

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.CountCallId );

      return asyncResult.GetCallResult<CountResult>();
    }

    private EstimateInt64 CalculateTotalCount(
      DataSourceGroupContext context,
      EstimateInt64 totalCount,
      EstimateInt64 collapsedCount,
      IList<EstimateInt64> groupCounts,
      IList<EstimateInt64> defaultHeadersCounts,
      IList<EstimateInt64> defaultFootersCounts )
    {
      if( totalCount == null )
        throw new ArgumentNullException( "totalCount" );

      EstimateInt64 adjustedCount = totalCount;

      adjustedCount = this.CalculateGroupCount( context, DataPath.Empty, totalCount, groupCounts, defaultHeadersCounts, defaultFootersCounts );

      if( collapsedCount != null )
      {
        adjustedCount -= collapsedCount;

        if( adjustedCount.Value < 0 )
        {
          Debug.Assert( adjustedCount.IsEstimate );
          adjustedCount = new EstimateInt64( -adjustedCount.Value, true );
        }
      }
      else
      {
        adjustedCount = adjustedCount.ToEstimate();
      }

      return adjustedCount;
    }

    #endregion

    #region Begin/End Count management Methods

    private IAsyncResult BeginResolveCountValues(
      DataSourceContext context,
      int endPositionDirection,
      EstimateInt64 remainingFetchCount,
      DataPath endPositionItem,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount,
      AsyncCallback userCallback,
      object asyncState )
    {
      ContextAsyncResult asyncResult = new ContextAsyncResult( GroupLevelEnumerator.ResolveCountCallId, userCallback, asyncState );

      bool needUpdateEndPositionOffset = ( endPositionOffset != null );
      bool needUpdateTotalCount = ( totalCount != null );
      DataSourceGroupContext groupContext = context.GroupContext;

      if( ( !needUpdateEndPositionOffset && !needUpdateTotalCount )
        || ( !GroupLevelEnumerator.AddOrRemoveItems( groupContext ) ) )
      {
        asyncResult.StartSync( new Tuple<RelativeOffset, EstimateInt64>( endPositionOffset, totalCount ) );
        return asyncResult;
      }

      if( groupContext.GroupCountPolicy == Data.GroupCountPolicy.DoNotHandleCounts )
      {
        asyncResult.StartSync( new Tuple<RelativeOffset, EstimateInt64>( endPositionOffset.ToEstimate(), totalCount.ToEstimate() ) );
        return asyncResult;
      }

      ResolveCountData countData = new ResolveCountData();
      countData.EndPositionOffset = endPositionOffset;
      countData.TotalCount = totalCount;
      countData.Context = groupContext;

      asyncResult.CallContext = countData;

      DependentCall countHeadersCall = null;
      DependentCall countFootersCall = null;
      DependentCall unfilteredCollpasedGroupsCountCall = null;
      DependentCall unfilteredGroupsCountCall = null;
      bool filteredCountsEqualUnfilteredCounts = false;
      bool anyHeaderOrFooter = groupContext.AnyHeaderFooter;


      countHeadersCall =
        new DependentCall(
          ( cb, st ) =>
          {
            Func<DataPath, IAsyncEnumerator> getEnumerator = ( dataPath ) => groupContext.GetHeadersEnumerator( dataPath );

            return this.BeginHeadersFootersCount( context, getEnumerator, 1, cb, st );
          },
          ( par, iar ) =>
          {
            countData.DefaultHeadersCounts = this.EndHeadersFootersCountForDepth( iar );

            return null;
          } );

      countFootersCall =
        new DependentCall(
          ( cb, st ) =>
          {
            Func<DataPath, IAsyncEnumerator> getEnumerator = ( dataPath ) => groupContext.GetFootersEnumerator( dataPath );

            return this.BeginHeadersFootersCount( context, getEnumerator, 1, cb, st );
          },
          ( par, iar ) =>
          {
            countData.DefaultFootersCounts = this.EndHeadersFootersCountForDepth( iar );

            return null;
          } );

      unfilteredGroupsCountCall =
        new DependentCall(
        ( cb, st ) => this.BeginSubGroupCounts( context, DataPath.Empty, cb, st ),
        ( par, iar ) =>
        {
          countData.UnfilteredGroupCounts = this.EndSubGroupCounts( iar );

          if( filteredCountsEqualUnfilteredCounts )
          {
            countData.FilteredGroupCounts = countData.UnfilteredGroupCounts;
          }

          return null;
        } );

      unfilteredCollpasedGroupsCountCall =
        new DependentCall(
        ( cb, st ) => this.BeginCollapsedGroupCount( context, null, false, false, countData.DefaultHeadersCounts, countData.DefaultFootersCounts, cb, st ),
        ( par, iar ) =>
        {
          countData.UnfilteredCollapsedGroupCount = this.EndCollapsedGroupCount( iar );

          if( filteredCountsEqualUnfilteredCounts )
          {
            countData.FilteredCollapsedGroupCount = countData.UnfilteredCollapsedGroupCount;
          }

          return null;
        } );


      if( anyHeaderOrFooter )
      {
        asyncResult.AddDependentCall( countHeadersCall );
        asyncResult.AddDependentCall( countFootersCall );
      }

      if( needUpdateEndPositionOffset )
      {
        bool fetchAndReferencePositionAreAligned = ( ( endPositionDirection > 0 ) == ( endPositionOffset.ReferencePosition == ReferencePoint.AfterLast ) );

        if( endPositionItem == null )
        {
          if( fetchAndReferencePositionAreAligned )
          {
            countData.FilteredCollapsedGroupCount = 0;
            countData.FilteredGroupCounts = Enumerable
              .Repeat( new EstimateInt64( 0, false ), groupContext.GroupDescriptions.Count )
              .ToList();
          }
          else
          {
            filteredCountsEqualUnfilteredCounts = true;
          }
        }
        else
        {
          countData.FilteredCollapsedGroupCountIncludeEndPosition = fetchAndReferencePositionAreAligned;
          countData.EndPositionItemCollapsedDepth = groupContext.GroupCollections.GetCollapsedDepth( endPositionItem );

          DependentCall filteredCollapsedCountCall = new DependentCall(
          ( cb, st ) => this.BeginCollapsedGroupCount(
            context,
            endPositionItem,
            countData.FilteredCollapsedGroupCountIncludeEndPosition,
            countData.EndPositionOffset.ReferencePosition == ReferencePoint.AfterLast,
            countData.DefaultHeadersCounts,
            countData.DefaultFootersCounts,
            cb,
            st ),
          ( par, iar ) =>
          {
            countData.FilteredCollapsedGroupCount = this.EndCollapsedGroupCount( iar );

            return null;
          } );

          // Get the SortPosition to use for the group count.
          SortPosition groupPosition = GroupLevelEnumerator.GetGroupPosition(
                                         context,
                                         m_groupHelper,
                                         endPositionItem,
                                         ( countData.EndPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst ) );

          DependentCall filteredGroupCountCall =
            new DependentCall(
              ( cb, st ) => this.BeginGroupCount( new CountParameters( groupPosition, context ), cb, st ),
              ( par, iar ) =>
              {
                GroupCountResult groupCount = this.EndGroupCount( iar );
                countData.FilteredGroupCounts = ( groupCount != null ) ? groupCount.GroupCounts : null;

                return null;
              } );

          if( anyHeaderOrFooter )
          {
            asyncResult.AddDependentCall( filteredGroupCountCall );
          }

          asyncResult.AddDependentCall( filteredCollapsedCountCall );
        }

        countData.EndPositionOffset = DataUtils.CalculateRelativeOffset( countData.EndPositionOffset, -remainingFetchCount.Value, false );

        if( remainingFetchCount.IsEstimate )
        {
          countData.EndPositionOffset = countData.EndPositionOffset.ToEstimate();
        }
      }

      if( needUpdateTotalCount || filteredCountsEqualUnfilteredCounts )
      {
        if( anyHeaderOrFooter )
        {
          asyncResult.AddDependentCall( unfilteredGroupsCountCall );
        }

        asyncResult.AddDependentCall( unfilteredCollpasedGroupsCountCall );
      }

      if( asyncResult.HasDependentCalls )
      {
        asyncResult.Start();
      }
      else
      {
        asyncResult.StartSync( null );
      }

      return asyncResult;
    }

    private Tuple<RelativeOffset, EstimateInt64> EndResolveCountValues( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.ResolveCountCallId );
      ContextAsyncResult contextAsyncResult = ( ContextAsyncResult )asyncResult;

      if( contextAsyncResult.CallContext == null )
        return contextAsyncResult.GetCallResult<Tuple<RelativeOffset, EstimateInt64>>();

      ResolveCountData countData = ( ResolveCountData )contextAsyncResult.CallContext;
      RelativeOffset endPositionOffset = countData.EndPositionOffset;
      EstimateInt64 totalCount = countData.TotalCount;
      DataSourceGroupContext context = countData.Context;

      if( endPositionOffset != null )
      {
        EstimateInt64 offset = endPositionOffset.EstimateValue;

        if( context.AnyHeaderFooter )
        {
          if( countData.FilteredGroupCounts != null )
          {
            IList<EstimateInt64> defaultHeadersCounts = countData.DefaultHeadersCounts;
            IList<EstimateInt64> defaultFootersCounts = countData.DefaultFootersCounts;

            if( endPositionOffset.ReferencePosition == ReferencePoint.AfterLast )
            {
              IList<EstimateInt64> tmpList = defaultHeadersCounts;
              defaultHeadersCounts = defaultFootersCounts;
              defaultFootersCounts = tmpList;
            }

            int collapsedDepth = countData.EndPositionItemCollapsedDepth;
            bool includedInCollapsedCount = countData.FilteredCollapsedGroupCountIncludeEndPosition;

            for( int i = 0; i < countData.FilteredGroupCounts.Count; i++ )
            {
              EstimateInt64 depthCount = countData.FilteredGroupCounts[ i ];
              EstimateInt64 headerCountPerGroup = defaultHeadersCounts[ i ];
              EstimateInt64 footerCountPerGroup = defaultFootersCounts[ i ];

              bool isDepthNotCollapsed = ( collapsedDepth == 0 || i < ( collapsedDepth - 1 ) );

              if( !includedInCollapsedCount || isDepthNotCollapsed )
              {
                if( depthCount.Value > 0 )
                {
                  offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount - 1, footerCountPerGroup ) );
                }
              }
              else
              {
                offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount, footerCountPerGroup ) );
              }

              if( !includedInCollapsedCount && !isDepthNotCollapsed )
              {
                if( depthCount.Value > 0 )
                {
                  offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount - 1, headerCountPerGroup ) );
                }
              }
              else
              {
                offset = this.AddEstimates( offset, this.MultiplyEstimates( depthCount, headerCountPerGroup ) );
              }
            }
          }
          else
          {
            offset = offset.ToEstimate();
          }
        }

        if( context.AnyCollapsedGroup )
        {
          if( countData.FilteredCollapsedGroupCount != null )
          {
            offset -= countData.FilteredCollapsedGroupCount;
          }
          else
          {
            offset = offset.ToEstimate();
          }
        }

        endPositionOffset = new RelativeOffset( offset, endPositionOffset.ReferencePosition );
      }

      if( totalCount != null )
      {
        totalCount = this.CalculateTotalCount(
          context,
          totalCount,
          countData.UnfilteredCollapsedGroupCount,
          countData.UnfilteredGroupCounts,
          countData.DefaultHeadersCounts,
          countData.DefaultFootersCounts );
      }

      return new Tuple<RelativeOffset, EstimateInt64>( endPositionOffset, totalCount );
    }

    private IAsyncResult BeginHeadersFootersCount(
      DataSourceContext context,
      Func<DataPath, IAsyncEnumerator> headerOrFooterEnumFactory,
      int depth,
      AsyncCallback userCallback,
      object asyncState )
    {
      AsyncResult asyncResult = new AsyncResult( GroupLevelEnumerator.HeadersFootersCountCallId, userCallback, asyncState );

      DataPath groupPath = new DataPath( Enumerable.Repeat( GroupDataItem.Any, depth ).ToArray() );

      IAsyncEnumerator headersOrFootersEnumerator = headerOrFooterEnumFactory( groupPath );

      List<EstimateInt64> result = new List<EstimateInt64>();

      asyncResult.AddDependentCall(
        ( cb, st ) => headersOrFootersEnumerator.BeginCount( new CountParameters( context ), cb, st ),
        ( par, iar ) =>
        {
          CountResult enumResult = headersOrFootersEnumerator.EndCount( iar );

          result.Add( ( enumResult != null ) ? enumResult.Count : null );

          return new DependentCallResult( result );
        } );

      if( depth < context.GroupContext.GroupDescriptions.Count )
      {
        asyncResult.AddDependentCall(
          ( cb, st ) => this.BeginHeadersFootersCount( context, headerOrFooterEnumFactory, depth + 1, cb, st ),
          ( par, iar ) =>
          {
            List<EstimateInt64> countResults = this.EndHeadersFootersCountForDepth( iar );

            if( ( m_disposed ) || ( countResults == null ) )
              return new DependentCallResult( null );

            result.AddRange( countResults );

            return new DependentCallResult( result );
          } );
      }

      asyncResult.Start();

      return asyncResult;
    }

    private List<EstimateInt64> EndHeadersFootersCountForDepth( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.HeadersFootersCountCallId );

      return asyncResult.GetCallResult<List<EstimateInt64>>();
    }

    private IAsyncResult BeginCollapsedGroupCount(
      DataSourceContext context,
      DataPath limit,
      bool isLimitIncluded,
      bool isLowerLimit,
      IList<EstimateInt64> defaultHeadersCount,
      IList<EstimateInt64> defaultFootersCount,
      AsyncCallback userCallback,
      object asyncState )
    {
      DataSourceGroupContext groupContext = context.GroupContext;
      int groupLevelCount = groupContext.GroupDescriptions.Count;

      if( defaultHeadersCount != null && defaultHeadersCount.Count != groupLevelCount )
        throw new ArgumentException( "The specified defaultHeadersCount must be equal to the size of the GroupDescriptions collection.", "defaultHeadersCount" );

      if( defaultFootersCount != null && defaultFootersCount.Count != groupLevelCount )
        throw new ArgumentException( "The specified defaultFootersCount must be equal to the size of the GroupDescriptions collection.", "defaultFootersCount" );

      AsyncResult asyncResult = new AsyncResult( GroupLevelEnumerator.RangeCountCallId, userCallback, asyncState );

      EstimateInt64 collapsedCountDiff = 0;
      IEnumerator<GroupExpansionState> collapsedGroups = groupContext.GroupCollections.GetGroupExpansionStateEnumerator();

      Func<EstimateInt64, DependentCallResult> getNextCall = null;
      DependentCall subCountCall = null;
      DependentCall subGroupCountsCall = null;
      bool anyHeaderOrFooter = groupContext.AnyHeaderFooter;

      subCountCall =
        new DependentCall(
          ( cb, st ) => this.BeginGroupItemCount( context, collapsedGroups.Current.Path, cb, st ),
          ( par, iar ) =>
          {
            EstimateInt64 currentGroupCount = this.EndGroupItemCount( iar );

            if( m_disposed )
              return new DependentCallResult( null );

            return ( !anyHeaderOrFooter || currentGroupCount == null || currentGroupCount.Value == 0 )
              ? getNextCall( currentGroupCount )
              : new DependentCallResult( currentGroupCount, subGroupCountsCall );
          } );

      subGroupCountsCall =
        new DependentCall(
          ( cb, st ) => this.BeginSubGroupCounts( context, collapsedGroups.Current.Path, cb, st ),
          ( par, iar ) =>
          {
            EstimateInt64 count = ( EstimateInt64 )iar.AsyncState;
            IList<EstimateInt64> groupCounts = this.EndSubGroupCounts( iar );

            if( m_disposed )
              return new DependentCallResult( null );

            EstimateInt64 currentGroupCount = this.CalculateGroupCount(
              groupContext,
              collapsedGroups.Current.Path,
              count,
              groupCounts,
              defaultHeadersCount,
              defaultFootersCount );

            return getNextCall( currentGroupCount );
          } );

      getNextCall = ( currentGroupCount ) =>
      {
        if( currentGroupCount == null )
        {
          collapsedCountDiff = collapsedCountDiff.ToEstimate();
        }
        else
        {
          if( currentGroupCount.Value != 0 )
          {
            currentGroupCount -= 1;
          }

          collapsedCountDiff += currentGroupCount;
        }

        DependentCall nextCall = null;
        while( nextCall == null && collapsedGroups.MoveNext() )
        {
          Debug.Assert( collapsedGroups.Current.State == GroupState.Collapsed );

          if( collapsedGroups.Current.State == GroupState.Expanded )
          {
            Debug.Assert( false, "Collapse all state not supported yet." );
            collapsedCountDiff = collapsedCountDiff.ToEstimate();
            continue;
          }

          DataPath collapsedGroup = collapsedGroups.Current.Path;

          int collapsedDepth = groupContext.GroupCollections.GetCollapsedDepth( collapsedGroup );

          if( collapsedDepth < collapsedGroup.Depth )
          {
            Debug.Assert( collapsedDepth != 0, "This is supposed to be a collapsed group." );
            continue;
          }

          if( limit != null )
          {
            bool isEqual = false;
            Func<bool> compareLimit =
              ( isLowerLimit )
              ? ( Func<bool> )( () => m_groupHelper.Compare( context, limit, collapsedGroup ) > 0 )
              : ( Func<bool> )( () => m_groupHelper.Compare( context, limit, collapsedGroup ) < 0 );

            isEqual = collapsedGroup.IsAncestorOf( limit )
              || collapsedGroup.IsDescendantOf( limit )
              || collapsedGroup.Equals( limit );

            if( ( isEqual && !isLimitIncluded )
              || ( !isEqual && compareLimit() ) )
              continue;
          }

          nextCall = subCountCall;
        }

        return new DependentCallResult( collapsedCountDiff, nextCall );
      };

      DependentCallResult firstResult = getNextCall( 0 );
      DependentCall firstCall = firstResult.NextCall;

      if( firstCall != null )
      {
        asyncResult.Start( firstCall.AsyncCall, firstCall.IntermediateCallback );
      }
      else
      {
        asyncResult.StartSync( firstResult.ResultData );
      }

      return asyncResult;
    }

    private EstimateInt64 EndCollapsedGroupCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupLevelEnumerator.RangeCountCallId );

      return asyncResult.GetCallResult<EstimateInt64>();
    }

    public IAsyncResult BeginGroupItemCount(
      DataSourceContext context,
      DataPath groupPath,
      AsyncCallback userCallback,
      object asyncState )
    {
      return this.BeginSubCall(
        GroupLevelEnumerator.SubCountCallId,
        ( param, cb, st ) => this.ChildEnumerator.BeginCount( param, cb, st ),
        ( iar ) =>
        {
          CountResult countResult = this.ChildEnumerator.EndCount( iar );

          return ( countResult != null ) ? countResult.Count : null;
        },
        ( ctx, dataPath ) => this.TryGetSubCount( ctx.GroupContext, dataPath ),
        context,
        groupPath,
        userCallback,
        asyncState );
    }

    public EstimateInt64 EndGroupItemCount( IAsyncResult asyncResult )
    {
      return ( EstimateInt64 )this.EndSubCall( asyncResult, GroupLevelEnumerator.SubCountCallId, m_subCountCache );
    }

    private IAsyncResult BeginSubGroupCounts(
      DataSourceContext context,
      DataPath groupPath,
      AsyncCallback userCallback,
      object asyncState )
    {
      return this.BeginSubCall<IList<EstimateInt64>>(
        GroupLevelEnumerator.SubGroupCountsCallId,
        ( param, cb, st ) => this.BeginGroupCount( param, cb, st ),
        ( iar ) =>
        {
          GroupCountResult result = this.EndGroupCount( iar );

          return ( result != null ) ? result.GroupCounts : null;
        },
        ( ctx, dataPath ) => this.TryGetSubGroupCounts( ctx.GroupContext, dataPath ),
        context,
        groupPath,
        userCallback,
        asyncState );
    }

    private IList<EstimateInt64> EndSubGroupCounts( IAsyncResult asyncResult )
    {
      return ( IList<EstimateInt64> )this.EndSubCall( asyncResult, GroupLevelEnumerator.SubGroupCountsCallId, m_subGroupCountsCache );
    }

    private IAsyncResult BeginSubCall<T>(
      Guid callId,
      Func<CountParameters, AsyncCallback, object, IAsyncResult> beginCall,
      Func<IAsyncResult, object> endCall,
      Func<DataSourceContext, DataPath, T> cacheAccess,
      DataSourceContext context,
      DataPath groupPath,
      AsyncCallback userCallback,
      object asyncState )
    {
      ContextAsyncResult asyncResult = new ContextAsyncResult( callId, userCallback, asyncState );
      asyncResult.CallContext = new Tuple<DataSourceContext, DataPath>( context, groupPath );

      T cachedValue = cacheAccess.Invoke( context, groupPath );

      if( cachedValue != null )
      {
        asyncResult.StartSync( cachedValue );
      }
      else
      {
        SortPosition groupPosition = GroupLevelEnumerator.GetGroupPosition( context, m_groupHelper, groupPath, false );
        CountParameters countParameters = new CountParameters( groupPosition, true, context );

        asyncResult.Start(
            ( cb, st ) => beginCall( countParameters, cb, st ),
            ( par, iar ) => new DependentCallResult( endCall( iar ) ) );
      }

      return asyncResult;
    }

    private object EndSubCall( IAsyncResult asyncResult, Guid callId, IDictionary cache )
    {
      asyncResult.ValidateCallEnd( callId );

      Tuple<DataSourceContext, DataPath> callContext = asyncResult.GetCallContext<Tuple<DataSourceContext, DataPath>>();
      DataSourceGroupContext context = callContext.Item1.GroupContext;
      DataPath groupPath = callContext.Item2;

      object result = asyncResult.GetCallResult<object>();

      if( result != null )
      {
        if( context.GroupCountPolicy == GroupCountPolicy.Cache
          || context.GroupCountPolicy == GroupCountPolicy.CacheAsEstimates )
        {
          if( cache.Contains( groupPath ) )
          {
            cache[ groupPath ] = result;
          }
          else
          {
            cache.Add( groupPath, result );
          }
        }
      }

      return result;
    }

    private EstimateInt64 CalculateGroupCount(
      DataSourceGroupContext context,
      DataPath groupPath,
      EstimateInt64 basicCount,
      IList<EstimateInt64> subLevelGroupCount,
      IList<EstimateInt64> defaultHeadersCount,
      IList<EstimateInt64> defaultFootersCount )
    {
      if( basicCount == null )
        throw new ArgumentNullException( "basicCount" );

      if( basicCount.Value > 0 )
      {
        int groupDepth = Math.Max( groupPath.Depth, 1 );

        if( context.AnyHeaderFooter )
        {
          if( defaultHeadersCount != null || defaultFootersCount != null )
          {
            Action<int, IList<EstimateInt64>> considerHeaderFooters =
              ( index, headersFootersCounts ) =>
              {
                EstimateInt64 headersFootersCount = null;
                EstimateInt64 groupsCount = null;

                if( headersFootersCounts != null )
                {
                  headersFootersCount = headersFootersCounts[ index - 1 ];
                }

                if( subLevelGroupCount != null )
                {
                  groupsCount = subLevelGroupCount[ index - 1 ];
                }

                EstimateInt64 levelHeadersFootersCount = this.MultiplyEstimates( headersFootersCount, groupsCount );

                basicCount = this.AddEstimates( basicCount, levelHeadersFootersCount );
              };

            for( int i = groupDepth; i <= context.GroupDescriptions.Count; i++ )
            {
              considerHeaderFooters( i, defaultHeadersCount );
              considerHeaderFooters( i, defaultFootersCount );
            }
          }

          if( defaultFootersCount == null || defaultFootersCount == null )
          {
            basicCount = basicCount.ToEstimate();
          }
        }
      }

      return basicCount;
    }

    private EstimateInt64 AddEstimates( EstimateInt64 first, EstimateInt64 second )
    {
      return this.EstimatesOperation( first, second, ( a, b ) => ( a + b ) );
    }

    private EstimateInt64 MultiplyEstimates( EstimateInt64 first, EstimateInt64 second )
    {
      return this.EstimatesOperation( first, second, ( a, b ) => ( a * b ) );
    }

    private EstimateInt64 EstimatesOperation( EstimateInt64 first, EstimateInt64 second, Func<long, long, long> operation )
    {
      if( first == null && second == null )
        return null;

      EstimateInt64 result = null;

      if( first == null )
      {
        result = second.ToEstimate();
      }
      else if( second == null )
      {
        result = first.ToEstimate();
      }
      else
      {
        result = new EstimateInt64( operation( first.Value, second.Value ), first.IsEstimate || second.IsEstimate );
      }

      return result;
    }

    private FilterExpressionBase CreateFilterForGroup( DataSourceGroupContext context, DataPath groupPath )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( groupPath.Depth == 0 )
        return null;

      List<FilterExpressionBase> groupFilters = new List<FilterExpressionBase>();
      for( int i = 0; i < groupPath.Depth; i++ )
      {
        Debug.Assert( i < context.GroupDescriptions.Count );

        GroupDescription group = context.GroupDescriptions[ i ];
        DataItem item = groupPath[ i ];

        Debug.Assert( item is GroupDataItem );

        groupFilters.Add( new FilterExpression( group.GetPropertyName(), FilterOperator.Equal, item.Data ) );
      }

      if( groupFilters.Count == 1 )
      {
        return groupFilters[ 0 ];
      }
      else
      {
        Debug.Assert( groupFilters.Count != 0 );
        return new AndFilterExpression( groupFilters );
      }
    }

    //private FilterExpressionBase CreateFilterForItem( DataPath itemPath, bool lowerLimit, bool includeLimit )
    //{
    //  
    //  SortDescription[] completeSort = this.GroupHelper.CompleteSort;
    //  List<object> sortValues = this.GroupHelper.ExtractSortValues( completeSort, itemPath ).ToList();

    //  return NavigationFilterUtils.CreateFilterExpression(
    //    completeSort.Take( sortValues.Count ).ToList(),
    //    sortValues,
    //    !lowerLimit,
    //    includeLimit );
    //}

    private IList<EstimateInt64> TryGetSubGroupCounts( DataSourceGroupContext context, DataPath groupPath )
    {
      Func<object, object> toEstimate = ( counts ) =>
        ( ( IList<EstimateInt64> )counts ).Select( ( value ) => value.ToEstimate() ).ToList();

      IList<EstimateInt64> result =
      ( IList<EstimateInt64> )
        this.TryGetCachedCountValue( m_subGroupCountsCache, context, toEstimate, groupPath );

      return result;
    }

    private EstimateInt64 TryGetSubCount( DataSourceGroupContext context, DataPath groupPath )
    {
      Func<object, object> toEstimate = ( count ) => ( ( EstimateInt64 )count ).ToEstimate();

      return ( EstimateInt64 )
        this.TryGetCachedCountValue( m_subCountCache, context, toEstimate, groupPath );
    }

    private object TryGetCachedCountValue( IDictionary cache, DataSourceGroupContext context, Func<object, object> toEstimate, DataPath groupPath )
    {
      if( cache == null )
        throw new ArgumentNullException( "cache" );

      if( context == null )
        throw new ArgumentNullException( "context" );

      object cachedValue = null;

      if( context.GroupCountPolicy == GroupCountPolicy.Cache
        || context.GroupCountPolicy == GroupCountPolicy.CacheAsEstimates )
      {
        if( cache.Contains( groupPath ) )
        {
          cachedValue = cache[ groupPath ];

          if( context.GroupCountPolicy == Data.GroupCountPolicy.CacheAsEstimates )
          {
            cachedValue = toEstimate( cachedValue );
          }
        }
      }

      return cachedValue;
    }

    #endregion Begin/End Count management Methods

    #region Change Handling management Methods

    protected override IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgs(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      IEnumerable<DataSourceChangedEventArgs> childEventArgs = base.TransformDataSourceChangedEventArgs( context, e );

      // If there is no grouping, nothing needs to be done.
      if( !context.GroupContext.AnyGroup )
        return childEventArgs;

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();

      foreach( DataSourceChangedEventArgs eventArgs in childEventArgs )
      {
        switch( eventArgs.Action )
        {
          case DataSourceChangedAction.Add:
            newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsAdded( context, eventArgs ) );
            break;

          case DataSourceChangedAction.Remove:
            newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsRemoved( context, eventArgs ) );
            break;

          case DataSourceChangedAction.ItemChanged:
            newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsChanged( context, eventArgs ) );
            break;

          case DataSourceChangedAction.Replace:
            newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsReplaced( context, eventArgs ) );
            break;

          case DataSourceChangedAction.Reset:
            newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsReset( context, eventArgs ) );
            break;

          default:
            throw new NotSupportedException();
        }
      }

      return newEventArgs;
    }

    private void UpdateCounts( DataSourceContext context, DataPath newPath, DataPath oldPath )
    {
      this.UpdateSubCounts( context, newPath, oldPath );
      this.UpdateSubGroupCounts( context, newPath, oldPath );
    }

    private void UpdateSubCounts( DataSourceContext context, DataPath newPath, DataPath oldPath )
    {
      foreach( DataPath path in new List<DataPath>( m_subCountCache.Keys ) )
      {

        bool haveUnknownValues = ( oldPath != null && oldPath.Depth < path.Depth );
        bool pathReferNewValue;
        bool pathMayReferOldValue;

        this.GetReferedPaths( context, path, newPath, oldPath, out pathReferNewValue, out pathMayReferOldValue );

        if( pathReferNewValue )
        {
          m_subCountCache[ path ] = m_subCountCache[ path ] + 1;
        }

        if( pathMayReferOldValue )
        {
          if( haveUnknownValues )
          {
            m_subCountCache.Remove( path );
          }
          else
          {
            EstimateInt64 newCount = m_subCountCache[ path ] - 1;

            if( newCount.Value <= 0 )
            {
              m_subCountCache.Remove( path );
            }
            else
            {
              m_subCountCache[ path ] = newCount;
            }
          }
        }
      }
    }

    private void UpdateSubGroupCounts( DataSourceContext context, DataPath newPath, DataPath oldPath )
    {
      foreach( DataPath path in new List<DataPath>( m_subGroupCountsCache.Keys ) )
      {
        bool pathReferNewValue;
        bool pathMayReferOldValue;

        this.GetReferedPaths( context, path, newPath, oldPath, out pathReferNewValue, out pathMayReferOldValue );

        if( pathReferNewValue || pathMayReferOldValue )
        {
          m_subGroupCountsCache.Remove( path );
        }
      }
    }

    private void GetReferedPaths(
      DataSourceContext context,
      DataPath path,
      DataPath newPath,
      DataPath oldPath,
      out bool pathReferNewPath,
      out bool pathMayReferOldPath )
    {
      if( context == null )
        throw new ArgumentNullException( "null" );

      pathReferNewPath = ( newPath != null );
      pathMayReferOldPath = ( oldPath != null );

      IList<string> groupNames = context.GroupContext.GroupNames.ToList();

      if( groupNames.Count < path.Depth )
        throw new DataInternalException();

      for( int i = 0; i < path.Depth; i++ )
      {
        string groupName = groupNames[ i ];
        object pathValue = path[ i ].Data;

        if( pathReferNewPath )
        {
          object newValue = m_groupHelper.GetMemberValue( context, groupName, newPath );
          pathReferNewPath = object.Equals( newValue, pathValue );
        }

        if( pathMayReferOldPath && ( i < oldPath.Depth ) )
        {
          object oldValue = m_groupHelper.GetMemberValue( context, groupName, oldPath );
          pathMayReferOldPath = object.Equals( oldValue, pathValue );
        }
      }
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsAdded(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Debug.Assert( e.Action == DataSourceChangedAction.Add );

      if( ( e.NewItems == null ) || ( e.NewItems.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      List<object> items = new List<object>();

      // Find out which items aren't in a collapsed group.
      foreach( DataPath item in e.NewItems.Cast<DataPath>() )
      {
        this.UpdateCounts( context, item, null );

        DataPath collapsedPath;
        long collapsedGroupCount;

        // The item isn't in a collapsed group.
        if( !this.TryGetCollapsedPathWithRealCount( context, item, out collapsedPath, out collapsedGroupCount ) )
        {
          items.Add( item );
        }
        // The item has created a new collapsed group.
        else if( collapsedGroupCount == 1 )
        {
          items.Add( collapsedPath );
        }
      }

      // All items are in collapsed groups.
      if( items.Count == 0 )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      DataSourceChangedEventArgs newEventArgs = new DataSourceChangedEventArgs( DataSourceChangedAction.Add, items, null );

      return new DataSourceChangedEventArgs[] { newEventArgs };
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsRemoved(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Debug.Assert( e.Action == DataSourceChangedAction.Remove );

      if( ( e.OldItems == null ) || ( e.OldItems.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      List<object> items = new List<object>();

      // Find out which items aren't in a collapsed group.
      foreach( DataPath item in e.OldItems.Cast<DataPath>() )
      {
        this.UpdateCounts( context, null, item );

        DataPath collapsedPath;
        long collapsedGroupCount;

        // The item isn't in a collapsed group.
        if( !this.TryGetCollapsedPathWithRealCount( context, item, out collapsedPath, out collapsedGroupCount ) )
        {
          items.Add( item );
        }
        // The item was the last item in the collapsed group.
        else if( collapsedGroupCount == 0 )
        {
          items.Add( collapsedPath );
        }
      }

      // All items are in collapsed groups.
      if( items.Count == 0 )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      DataSourceChangedEventArgs newEventArgs = new DataSourceChangedEventArgs( DataSourceChangedAction.Remove, null, items );

      return new DataSourceChangedEventArgs[] { newEventArgs };
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsReplaced(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Debug.Assert( e.Action == DataSourceChangedAction.Replace );
      Debug.Assert( e.OldItems.Count == e.NewItems.Count );

      if( e.OldItems.Count == 0 )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();
      int count = e.OldItems.Count;

      // Find out which items aren't in a collapsed group.
      for( int i = 0; i < count; i++ )
      {
        DataPath oldItem = ( DataPath )e.OldItems[ i ];
        DataPath newItem = ( DataPath )e.NewItems[ i ];

        newEventArgs.AddRange( this.TransformItemReplaced( context, oldItem, newItem ) );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsReset(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.Reset );

      m_subCountCache.Clear();

      return new DataSourceChangedEventArgs[] { e };
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsChanged(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.ItemChanged );

      if( ( e.ItemsChanged == null ) || ( e.ItemsChanged.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      DataSourceGroupContext groupContext = context.GroupContext;
      int groupCount = groupContext.GroupDescriptions.Count;
      GroupLevelCollection groupCollections = groupContext.GroupCollections;

      List<ItemChangedDescription> itemsChanged = new List<ItemChangedDescription>();
      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();

      foreach( ItemChangedDescription itemChangedInfo in e.ItemsChanged )
      {
        DataPath targetPath = ( DataPath )itemChangedInfo.Item;

        Debug.Assert( targetPath.Depth >= groupCount );

        DataPath oldPath = this.ReCreateOldPath( context, itemChangedInfo );

        // The change has no impact on the grouping.
        if( object.Equals( oldPath, targetPath ) )
        {
          // The item isn't hidden behind a collapsed group.
          if( !groupCollections.IsCollapsed( targetPath ) )
          {
            itemsChanged.Add( itemChangedInfo );
          }
        }
        // The old path was recreated successfully.
        else if( oldPath.Depth == targetPath.Depth )
        {
          newEventArgs.AddRange( this.TransformItemChanged( context, oldPath, targetPath ) );
        }
        // The old path was recreated partially.
        else
        {
          IEnumerable<DataSourceChangedEventArgs> newDataSourceChanges;

          if( !this.TryTransformIncompleteItemChanged( context, oldPath, targetPath, out newDataSourceChanges ) )
          {
            itemsChanged.Add( itemChangedInfo );
          }
          else
          {
            newEventArgs.AddRange( newDataSourceChanges );
          }
        }
      }

      if( itemsChanged.Count > 0 )
      {
        newEventArgs.Add( new DataSourceChangedEventArgs( itemsChanged ) );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformItemReplaced(
      DataSourceContext context,
      DataPath oldItem,
      DataPath newItem )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( oldItem == null )
        throw new ArgumentNullException( "oldItem" );

      if( newItem == null )
        throw new ArgumentNullException( "newItem" );

      this.UpdateCounts( context, newItem, oldItem );

      DataPath oldCollapsedPath;
      DataPath newCollapsedPath;
      long oldCollapsedGroupCount;
      long newCollapsedGroupCount;

      bool isOldItemCollapsed = this.TryGetCollapsedPathWithRealCount( context, oldItem, out oldCollapsedPath, out oldCollapsedGroupCount );
      bool isNewItemCollapsed = this.TryGetCollapsedPathWithRealCount( context, newItem, out newCollapsedPath, out newCollapsedGroupCount );

      // The item has simply been replaced by another none collapsed item.
      if( !isNewItemCollapsed && !isOldItemCollapsed )
      {
        yield return new DataSourceChangedEventArgs(
          DataSourceChangedAction.Replace,
          new DataPath[] { newItem },
          new DataPath[] { oldItem } );
      }
      else if( !isNewItemCollapsed && isOldItemCollapsed )
      {
        // The last item of a collapsed group has been replaced by an none collpased item.
        if( oldCollapsedGroupCount == 0 )
        {
          yield return new DataSourceChangedEventArgs(
            DataSourceChangedAction.Remove,
            null,
            new DataPath[] { oldCollapsedPath } );
        }

        yield return new DataSourceChangedEventArgs(
            DataSourceChangedAction.Add,
            new DataPath[] { newItem },
            null );
      }
      else if( isNewItemCollapsed && !isOldItemCollapsed )
      {
        // The new item has created a new collapsed group.
        if( newCollapsedGroupCount == 1 )
        {
          yield return new DataSourceChangedEventArgs(
            DataSourceChangedAction.Add,
            new DataPath[] { newCollapsedPath },
            null );
        }

        yield return new DataSourceChangedEventArgs(
            DataSourceChangedAction.Remove,
            null,
            new DataPath[] { oldItem } );
      }
      else if( !object.Equals( oldCollapsedPath, newCollapsedPath ) )
      {
        // The last item of a collapsed group has been replaced by another collapsed group.
        if( oldCollapsedGroupCount == 0 )
        {
          yield return new DataSourceChangedEventArgs(
            DataSourceChangedAction.Remove,
            null,
            new DataPath[] { oldCollapsedPath } );
        }

        // The new collapsed item has created a new collapsed group.
        if( newCollapsedGroupCount == 1 )
        {
          yield return new DataSourceChangedEventArgs(
            DataSourceChangedAction.Add,
            new DataPath[] { newCollapsedPath },
            null );
        }
      }
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformItemChanged(
      DataSourceContext context,
      DataPath oldItem,
      DataPath newItem )
    {
      return this.TransformItemReplaced( context, oldItem, newItem );
    }

    private bool TryTransformIncompleteItemChanged(
      DataSourceContext context,
      DataPath oldItem,
      DataPath newItem,
      out IEnumerable<DataSourceChangedEventArgs> eventArgs )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( oldItem == null )
        throw new ArgumentNullException( "oldItem" );

      if( newItem == null )
        throw new ArgumentNullException( "newItem" );

      eventArgs = null;

      this.UpdateCounts( context, newItem, oldItem );

      DataPath oldCollapsedPath;
      long oldCollapsedGroupCount;

      // We cannot guess anything with an incomplete old path that isn't part of a collapsed group.
      if( !this.TryGetCollapsedPathWithRealCount( context, oldItem, out oldCollapsedPath, out oldCollapsedGroupCount ) )
        return false;

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();
      eventArgs = newEventArgs;

      DataPath newCollapsedPath;
      long newCollapsedGroupCount;
      bool isNewItemCollapsed = this.TryGetCollapsedPathWithRealCount( context, newItem, out newCollapsedPath, out newCollapsedGroupCount );

      DataPath itemAdded = null;
      DataPath itemRemoved = null;

      if( !isNewItemCollapsed )
      {
        // The last item of a collapsed group has been replaced by an none collpased item.
        if( oldCollapsedGroupCount == 0 )
        {
          itemRemoved = oldCollapsedPath;
        }

        itemAdded = newItem;
      }
      else if( isNewItemCollapsed )
      {
        // The new item has created a new collapsed group.
        if( newCollapsedGroupCount == 1 )
        {
          itemAdded = newCollapsedPath;
        }
      }
      else if( !object.Equals( oldCollapsedPath, newCollapsedPath ) )
      {
        // The last item of a collapsed group has been replaced by another collapsed group.
        if( oldCollapsedGroupCount == 0 )
        {
          itemRemoved = oldCollapsedPath;
        }

        // The new collapsed item has created a new collapsed group.
        if( newCollapsedGroupCount == 1 )
        {
          itemAdded = newCollapsedPath;
        }
      }

      if( itemAdded != null )
      {
        newEventArgs.Add(
          new DataSourceChangedEventArgs(
            DataSourceChangedAction.Add,
            new DataPath[] { itemAdded },
            null ) );
      }

      if( itemRemoved != null )
      {
        newEventArgs.Add(
          new DataSourceChangedEventArgs(
            DataSourceChangedAction.Remove,
            null,
            new DataPath[] { itemRemoved } ) );
      }

      return true;
    }

    private DataPath ReCreateOldPath( DataSourceContext context, ItemChangedDescription itemChangedInfo )
    {
      string[] groupNames = context.GroupContext.GroupNames.ToArray();
      bool groupValuesChanged = false;

      // Find out if a change occured on a grouped property.
      foreach( PropertyChangedDescription propertyChanged in itemChangedInfo.ChangedProperties )
      {
        if( groupNames.Contains( propertyChanged.PropertyName ) )
        {
          groupValuesChanged = true;
          break;
        }
      }

      DataPath targetPath = ( DataPath )itemChangedInfo.Item;

      // The change has no impact on the grouping.
      if( !groupValuesChanged )
        return targetPath;

      Func<DataSourceContext, DataPath, DataPath> cachedPathFinder = this.CachedPathProvider;

      // Try to retrieve the path from cache.
      if( cachedPathFinder != null )
      {
        DataPath cachedPath = cachedPathFinder.Invoke( context, targetPath );

        // The path is from the same context.
        if( ( cachedPath != null ) && ( cachedPath.Depth != targetPath.Depth ) )
          return cachedPath;
      }

      DataItem[] dataItems = new DataItem[ targetPath.Depth ];
      int length = 0;

      for( int i = 0; i < groupNames.Length; i++ )
      {
        string groupName = groupNames[ i ];
        object oldValue = null;
        bool oldValueFound = false;

        foreach( PropertyChangedDescription propertyChanged in itemChangedInfo.ChangedProperties )
        {
          if( propertyChanged.PropertyName == groupName )
          {
            oldValue = propertyChanged.OldValue;
            oldValueFound = ( oldValue != PropertyChangedDescription.UnsetValue );

            break;
          }
        }

        if( !oldValueFound )
          break;

        dataItems[ i ] = new GroupDataItem( oldValue );
        length = i + 1;
      }

      if( ( length == groupNames.Length ) && ( length == targetPath.Depth + 1 ) )
      {
        dataItems[ length ] = targetPath[ length ];
        length++;
      }

      return new DataPath( dataItems, length );
    }

    private bool TryGetCollapsedPathWithRealCount(
      DataSourceContext context,
      DataPath dataPath,
      out DataPath collapsedPath,
      out long collapsedCount )
    {
      collapsedPath = null;
      collapsedCount = -1;
      DataSourceGroupContext groupContext = context.GroupContext;
      int collapsedDepth = groupContext.GroupCollections.GetCollapsedDepth( dataPath );

      if( collapsedDepth > 0 )
      {
        //If the item is the only one in the collapsed group, it result in a group add
        collapsedPath = ( collapsedDepth < dataPath.Depth )
          ? dataPath.CreateAncestorPath( collapsedDepth )
          : dataPath;

        EstimateInt64 collapsedGroupCount = this.TryGetSubCount( groupContext, collapsedPath );

        if( ( collapsedGroupCount == null )
          && ( groupContext.GroupCountPolicy == GroupCountPolicy.Query )
          && ( this.IsFullySynchronized ) )
        {
          collapsedGroupCount = this.EndGroupItemCount( this.BeginGroupItemCount( context, collapsedPath, null, null ) );
        }

        if( ( collapsedGroupCount != null ) && ( !collapsedGroupCount.IsEstimate ) )
        {
          collapsedCount = collapsedGroupCount.Value;
          Debug.Assert( collapsedCount >= 0 );
        }
      }

      return ( collapsedPath != null );
    }

    #endregion

    internal static bool AddOrRemoveItems( DataSourceGroupContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return ( context.AnyCollapsedGroup )
          || ( context.AnyHeaderFooter );
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    protected override SourcePosition ConvertSourcePosition(
      DataSourceContext context,
      SourcePosition sourcePosition,
      long expectedMoveCount,
      int expectedFetchCount )
    {
      ItemPosition itemPosition = sourcePosition as ItemPosition;
      if( itemPosition != null )
      {
        DataPath targetItem = ( DataPath )itemPosition.Item;
        if( m_groupHelper.IsGroupPath( context, targetItem ) )
          return this.CreateSortPosition( context, targetItem, expectedMoveCount, expectedFetchCount );
      }

      return base.ConvertSourcePosition( context, sourcePosition, expectedMoveCount, expectedFetchCount );
    }

    protected override DataPath Convert( DataSourceContext context, DataPath value )
    {
      return m_groupHelper.Convert( context, value );
    }

    protected override DataPath ConvertBack( DataSourceContext context, DataPath value )
    {
      return m_groupHelper.ConvertBack( context, value );
    }

    private static DataPath GetHeaderPath( DataPath path )
    {
      DataPath headerPath = null;

      // Handle any header/footer of this depth.
      for( int i = 0; i < path.Depth; i++ )
      {
        if( path[ i ] is HeaderFooterDataItem )
        {
          headerPath = path.CreateAncestorOrSamePath( i + 1 );
          break;
        }
      }

      return headerPath;
    }

    private static void ExtractFetchSourcePath(
      JumpMoveAndFetchParameters parameters,
      out DataPath completeHeaderPath,
      out DataPath startingPath,
      out Nullable<StartPosition> startPosition )
    {
      DataSourceGroupContext groupContext = parameters.Context.GroupContext;
      SourcePosition sourcePosition = parameters.SourcePosition;

      startingPath = null;
      startPosition = null;
      completeHeaderPath = null;

      if( sourcePosition is ItemPosition )
      {
        startingPath = ( DataPath )( ( ItemPosition )sourcePosition ).Item;
        DataPath headerPath = GroupLevelEnumerator.GetHeaderPath( startingPath );

        if( headerPath != null )
        {
          HeaderFooterDataItem headerItem = ( HeaderFooterDataItem )headerPath.LastChild;

          //Header is a group concept. exclude it an fetch the underling group.
          completeHeaderPath = startingPath;
          startingPath = headerPath.ParentPath;

          //A footer when moving forward, or a header when moving backward must be excluded
          if( parameters.ExpectedMoveCount != 0 )
          {
            completeHeaderPath = null;
            if( ( parameters.ExpectedMoveCount < 0 ) == headerItem.IsHeader )
            {
              startPosition = ( parameters.ExpectedMoveCount > 0 )
                ? StartPosition.After
                : StartPosition.Before;
            }
          }
        }

        if( ( startPosition == null )
          && ( startingPath.Depth <= groupContext.GroupDescriptions.Count ) )
        {
          //If the item refer to a group, it must be converted to a SortPosition.
          //Include the item in the fetch.
          startPosition = DataUtils.IsQueryingForward( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount )
            ? StartPosition.Before
            : StartPosition.After;
        }
      }
      else if( sourcePosition is SortPosition )
      {
        GroupLevelEnumerator.CreatePathFromSortPosition( groupContext, ( SortPosition )sourcePosition, out startingPath, out startPosition );
      }

      if( ( startingPath != null ) && ( parameters.ExpectedMoveCount != 0 ) )
      {
        int collapsedDepth = groupContext.GroupCollections.GetCollapsedDepth( startingPath );

        if( collapsedDepth > 0 )
        {
          completeHeaderPath = null;
          startingPath = startingPath.CreateAncestorOrSamePath( collapsedDepth );
          startPosition = ( parameters.ExpectedMoveCount > 0 )
            ? StartPosition.After
            : StartPosition.Before;
        }
      }
    }

    private static JumpMoveAndFetchParameters OptimizeParameters( JumpMoveAndFetchParameters parameters )
    {
      DataPath startingPath = null;
      Nullable<StartPosition> startPosition = null;
      DataPath completeHeaderPath = null;

      GroupLevelEnumerator.ExtractFetchSourcePath(
        parameters,
        out completeHeaderPath,
        out startingPath,
        out startPosition );

      if( startPosition != null )
      {
        IList<SortDescription> sortOrder = parameters.Context.GetCurrentCompleteSortOrder().ToList();

        SourcePosition startingSourcePosition =
          new SortPosition(
            sortOrder,
            startingPath.Path.Select( ( item ) => item.Data ).ToList(),
            startPosition.Value );

        parameters = parameters.CreateWithNewSourcePosition( startingSourcePosition );
      }

      return parameters;
    }

    private static SortPosition GetGroupPosition( DataSourceContext context, IContextualGroupDataHelper groupHelper, DataPath dataPath, bool reverse )
    {
      if( groupHelper == null )
        throw new ArgumentNullException( "groupHelper" );

      string[] groupNames = context.GroupContext.GroupNames.ToArray();
      int groupCount = groupNames.Length;
      IEnumerable<SortDescription> sortOrder = context.GetCurrentSortOrder().Take( groupCount );

      Debug.Assert( groupNames.SequenceEqual( sortOrder.Select( ( sd ) => sd.PropertyName ) ) );

      if( reverse )
      {
        sortOrder = ( from so in sortOrder
                      select new SortDescription(
                               so.PropertyName,
                               ( so.Direction == ListSortDirection.Ascending )
                                 ? ListSortDirection.Descending
                                 : ListSortDirection.Ascending ) );
      }

      List<SortDescription> groupOrder = sortOrder.ToList();
      List<object> groupValues = new List<object>( groupCount );

      if( dataPath.Depth > 0 )
      {
        foreach( string groupName in groupNames )
        {
          object value;

          if( !groupHelper.TryGetMemberValue( context, groupName, dataPath, out value ) )
            break;

          groupValues.Add( value );
        }
      }

      return new SortPosition( groupOrder, groupValues, StartPosition.Before );
    }

    private static void CreatePathFromSortPosition(
      DataSourceGroupContext context,
      SortPosition sortPosition,
      out DataPath path,
      out Nullable<StartPosition> startPosition )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( sortPosition == null )
        throw new ArgumentNullException( "sortPosition" );

      int sortValueCount = sortPosition.SortValues.Count;
      int pathDepth = Math.Min( sortValueCount, context.GroupDescriptions.Count );
      DataItem[] pathItems = new DataItem[ pathDepth ];
      for( int i = 0; i < pathDepth; i++ )
      {
        pathItems[ i ] = new GroupDataItem( sortPosition.SortValues[ i ] );
      }

      startPosition = ( pathDepth == sortValueCount )
        ? sortPosition.StartPosition
        : ( Nullable<StartPosition> )null;

      path = new DataPath( pathItems );
    }

    private DependentCallResult CompleteOperation(
      GroupAsyncResult groupAsyncResult,
      ChildResultEnumerator childResultEnumerator )
    {
      if( ( childResultEnumerator.HasCurrent ) && ( groupAsyncResult.RemainingFetchCount != 0 ) )
        throw new DataInternalException();

      DataPath endPositionItem;
      RelativeOffset endPositionOffset = childResultEnumerator.CurrentItemPositionOffset;
      EstimateInt64 remainingFetchCount;
      EstimateInt64 totalCount = childResultEnumerator.ChildResult.TotalCount;
      bool result;

      long? actualMoveCount;
      ICollection<DataPath> fetchedItems;
      SourcePosition endPosition;

      Debug.Assert( groupAsyncResult.ExpectedFetchCount != 0 );

      if( !childResultEnumerator.HasCurrent )
      {
        //End of source reached.
        endPositionItem = null;
      }
      else
      {
        endPositionItem = childResultEnumerator.Current;
      }

      if( groupAsyncResult.CumulativeFetch.Count > 0 )
      {
        actualMoveCount = groupAsyncResult.ExpectedMoveCount;
        fetchedItems = groupAsyncResult.CumulativeFetch;
      }
      else
      {
        //Failed move.
        Debug.Assert( !childResultEnumerator.HasCurrent );
        actualMoveCount = childResultEnumerator.ChildResult.ActualMoveCount;
        fetchedItems = null;
      }

      if( groupAsyncResult.RemainingFetchCount == 0 )
      {
        result = true;
        endPosition = new ItemPosition( groupAsyncResult.CumulativeFetch.Last() );
        remainingFetchCount = groupAsyncResult.CumulativeFetchExtent - groupAsyncResult.CumulativeFetchCount;

        if( endPositionItem == null )
        {
          remainingFetchCount += groupAsyncResult.GetFetchUnit();
        }
      }
      else
      {
        Debug.Assert( groupAsyncResult.CumulativeFetchExtent == groupAsyncResult.CumulativeFetchCount );
        Debug.Assert( !childResultEnumerator.HasCurrent );
        result = false;
        endPosition = childResultEnumerator.ChildResult.EndPositionObject;
        remainingFetchCount = 0;
      }

      int endPositionDirection;

      if( fetchedItems != null )
      {
        endPositionDirection = groupAsyncResult.GetFetchUnit();
      }
      else if( groupAsyncResult.SourcePosition.IsOutOfBound && groupAsyncResult.ExpectedMoveCount == 0 )
      {
        endPositionDirection = ( groupAsyncResult.SourcePosition.IsBeforeFirst )
          ? -1
          : 1;
      }
      else if( groupAsyncResult.ExpectedMoveCount != 0 )
      {
        endPositionDirection = groupAsyncResult.GetMoveUnit();
      }
      else
      {
        endPositionDirection = groupAsyncResult.GetFetchUnit();
      }

      DataSourceContext context = groupAsyncResult.Context;

      return new DependentCallResult(
        null,
        ( cb, st ) => this.BeginResolveCountValues(
          context,
          endPositionDirection,
          remainingFetchCount,
          endPositionItem,
          endPositionOffset,
          totalCount,
          cb,
          st ),
        ( par, iar ) =>
        {
          Tuple<RelativeOffset, EstimateInt64> countResult = this.EndResolveCountValues( iar );

          if( ( m_disposed ) || ( countResult == null ) )
            return new DependentCallResult( null );

          return new DependentCallResult(
            new JumpMoveAndFetchResult<DataPath>(
              groupAsyncResult,
              actualMoveCount,
              fetchedItems,
              result,
              endPosition,
              countResult.Item1,
              countResult.Item2 ) );
        } );
    }

    private SourcePosition CreateSortPosition(
      DataSourceContext context,
      DataPath dataPath,
      long expectedMoveCount,
      int expectedFetchCount )
    {
      StartPosition startPosition;

      if( expectedMoveCount > 0 )
      {
        startPosition = StartPosition.Before;
      }
      else if( ( expectedMoveCount < 0 ) || ( expectedFetchCount < 0 ) )
      {
        startPosition = StartPosition.After;
      }
      else
      {
        startPosition = StartPosition.Before;
      }

      return m_groupHelper.CreateSortPosition( context, dataPath, startPosition );
    }

    #region Private Fields

    private Dictionary<DataPath, EstimateInt64> m_subCountCache = new Dictionary<DataPath, EstimateInt64>();
    private Dictionary<DataPath, IList<EstimateInt64>> m_subGroupCountsCache = new Dictionary<DataPath, IList<EstimateInt64>>();
    private readonly IContextualGroupDataHelper m_groupHelper;
    private bool m_disposed; //false

    #endregion

    #region GroupAsyncResult Nested Type

    private class GroupAsyncResult : JumpMoveAndFetchAsyncResult
    {
      internal GroupAsyncResult( Guid callId, AsyncCallback asyncCallback, object asyncState, JumpMoveAndFetchParameters parameters )
        : base( callId, asyncCallback, asyncState, parameters )
      {
        this.CumulativeFetch = new List<DataPath>();
        this.CumulativeFetchExtent = 0;
      }

      public long CumulativeFetchCount
      {
        get
        {
          return this.CumulativeFetch.Count * this.GetFetchUnit();
        }
      }

      public EstimateInt64 CumulativeFetchExtent
      {
        get;
        private set;
      }

      public List<DataPath> CumulativeFetch
      {
        get;
        private set;
      }

      public int RemainingFetchCount
      {
        get
        {
          return this.ExpectedFetchCount - ( this.CumulativeFetch.Count * this.GetFetchUnit() );
        }
      }

      public void AddFetchResult( SimpleFetchResult fetchResult )
      {
        this.CumulativeFetch.AddRange( fetchResult.FetchedItems );

        if( this.CumulativeFetch.Count > Math.Abs( this.ExpectedFetchCount ) )
          throw new DataInternalException();

        if( fetchResult.InlineCount != null )
        {
          this.CumulativeFetchExtent = new EstimateInt64(
            this.CumulativeFetchExtent.Value + ( fetchResult.InlineCount.Value * this.GetFetchUnit() ),
            this.CumulativeFetchExtent.IsEstimate || this.CumulativeFetchExtent.IsEstimate );
        }
        else
        {
          this.CumulativeFetchExtent = this.CumulativeFetchExtent.ToEstimate();
        }
      }
    }

    #endregion

    #region ChildItemEnumerator Nested Type

    private class ChildResultEnumerator
    {
      internal ChildResultEnumerator( JumpMoveAndFetchResult<DataPath> childResult )
      {
        if( childResult == null )
          throw new ArgumentNullException( "childResult" );

        m_childResult = childResult;
        if( m_childResult.FetchedItems != null )
        {
          m_childEnumerator = m_childResult.FetchedItems.GetEnumerator();
          this.HasCurrent = m_childEnumerator.MoveNext();
        }
        else
        {
          m_childEnumerator = null;
          this.HasCurrent = false;
        }
      }

      public JumpMoveAndFetchResult<DataPath> ChildResult
      {
        get
        {
          return m_childResult;
        }
      }

      public bool HasCurrent
      {
        get;
        private set;
      }

      public DataPath Current
      {
        get
        {
          return ( this.HasCurrent )
            ? m_childEnumerator.Current
            : null;
        }
      }

      public RelativeOffset CurrentItemPositionOffset
      {
        get
        {
          if( m_childResult == null || m_childResult.EndPositionOffset == null )
            return null;

          if( !this.HasCurrent )
            return m_childResult.EndPositionOffset;

          int index = -1;
          foreach( DataPath item in m_childResult.FetchedItems )
          {
            index++;
            if( object.Equals( item, this.Current ) )
              break;
          }

          Debug.Assert( index != -1 );

          int endOffset = ( m_childResult.FetchedItems.Count - 1 ) - index;

          if( !m_childResult.Result )
            endOffset++;

          return DataUtils.CalculateRelativeOffset(
            m_childResult.EndPositionOffset,
            -( endOffset * m_childResult.GetFetchUnit() ) );
        }
      }

      public bool MoveNext()
      {
        if( this.HasCurrent )
        {
          this.HasCurrent = m_childEnumerator.MoveNext();
        }

        return this.HasCurrent;
      }

      private JumpMoveAndFetchResult<DataPath> m_childResult; //null
      private IEnumerator<DataPath> m_childEnumerator; //null
    }

    #endregion

    #region SimpleFetchResult Nested Type

    private class SimpleFetchResult
    {
      private static readonly ICollection<DataPath> EmptyCollection = new ReadOnlyCollection<DataPath>( new List<DataPath>() );

      internal SimpleFetchResult()
      {
        m_result = new List<DataPath>();
        m_resultReadOnly = new ReadOnlyCollection<DataPath>( m_result );
        m_count = 0;
      }

      public SimpleFetchResult( IEnumerable<DataPath> items, EstimateInt64 count )
      {
        if( items == null )
          throw new ArgumentNullException( "items" );

        m_result = items.ToList();
        m_resultReadOnly = new ReadOnlyCollection<DataPath>( m_result );
        m_count = count;
      }

      public void Add( SimpleFetchResult otherResult )
      {
        this.Add( otherResult.m_result, otherResult.m_count );
      }

      public void Add( IEnumerable<DataPath> items, EstimateInt64 count )
      {
        if( items == null )
          throw new ArgumentNullException( "items" );

        m_result.AddRange( items );
        if( m_count == null )
        {
          m_count = count.ToEstimate();
        }
        else if( count == null )
        {
          m_count = m_count.ToEstimate();
        }
        else
        {
          m_count += count;
        }
      }

      public ICollection<DataPath> FetchedItems
      {
        get
        {
          return m_resultReadOnly;
        }
      }

      public EstimateInt64 InlineCount
      {
        get
        {
          return m_count;
        }
      }

      private List<DataPath> m_result; //null
      private ReadOnlyCollection<DataPath> m_resultReadOnly; //null
      private EstimateInt64 m_count; //null
    }

    #endregion

    #region ResolveCountData Nested Type

    private class ResolveCountData
    {
      public IList<EstimateInt64> DefaultHeadersCounts; //null
      public IList<EstimateInt64> DefaultFootersCounts; //null
      public EstimateInt64 FilteredCollapsedGroupCount; //null
      public IList<EstimateInt64> FilteredGroupCounts; //null
      public EstimateInt64 UnfilteredCollapsedGroupCount; //null
      public IList<EstimateInt64> UnfilteredGroupCounts; //null

      public bool FilteredCollapsedGroupCountIncludeEndPosition; //false
      public int EndPositionItemCollapsedDepth; //0

      public RelativeOffset EndPositionOffset;//null
      public EstimateInt64 TotalCount; //null

      public DataSourceGroupContext Context; //null
    }

    #endregion
  }
}
